using System;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Reflection;
using MessageServer.utilities;

namespace MessageServer.serialize
{
    /**
     * To implement different Endian Formatters such as LittleEndian
     * or BingEndian, implement this interface. The data for which 
     * this interface is defined has to be a IConvertible interface
     * which typically is a primitive data type.
     * */
    public interface EndianFormatter<T> where T:IConvertible
    {
        /**
         * This function provides the conversion of the given data of type
         * T to a byte array. This will be used during serialization.
         * */
        byte[] convert(T data);

        /**
         * This function provides the conversion from byte array to the given
         * type T. This will be used during de-serialization.
         * */
        T convert(byte[] data);
    }

    /**
     * Formatter is a registry of the different implementation for the various
     * primitive data types. This currently uses the littleEndian format for
     * formatting the data.
     * */
    public class Formatter
    {
        Hashtable _converters;  //Stores the converters that will be used for different types

        //Constructor that creates the converters for the different types.
        public Formatter(bool lsb)
        {
            if (lsb)
            {
                _converters = new Hashtable();

                _converters.Add(typeof(UInt16), new UInt16Formatter());
                _converters.Add(typeof(UInt32), new UInt32Formatter());
                _converters.Add(typeof(Boolean), new BooleanFormatter());
                _converters.Add(typeof(Byte), new ByteFormatter());
                _converters.Add(typeof(string), new StringFormatter(this)); //hsa to be there for every
                _converters.Add(typeof(Int16), new Int16Formatter());
                _converters.Add(typeof(Int32), new Int32Formatter());
                _converters.Add(typeof(float), new LittleEndianFormatter<float>());
                _converters.Add(typeof(double), new LittleEndianFormatter<double>());
            }
            else
            {
                //need to use the BigEndianFormatter.
            }
        }

        /**
         * Another utterly useless feature where can't cast to the base class template!!! Hence
         * have to use this hack. So have defined a new interface which derives from the specifi
         * template and exposes the correct functions.
         * 
         * This function returns the formatter for the given type. It looks up the registered
         * formatter in the hashtable.
         * */
        public ObjectFormatter formatterfor(Type t) 
        {
            return _converters[t] as ObjectFormatter;
        }

        //returns true if the given type is currently a supported type, else false.
        public bool supports(Type t)
        {
            return _converters.ContainsKey(t);
        }

        //convienience functions
        public ObjectFormatter UINT16 { get { return _converters[typeof(UInt16)] as ObjectFormatter; } }
        public ObjectFormatter UINT32 { get { return _converters[typeof(UInt32)] as ObjectFormatter; } }
        public ObjectFormatter BOOLEAN { get { return _converters[typeof(Boolean)] as ObjectFormatter; } }
        public ObjectFormatter BYTE { get { return _converters[typeof(Byte)] as ObjectFormatter; } }
        public ObjectFormatter STRING { get { return _converters[typeof(string)] as ObjectFormatter; } }
        public ObjectFormatter INT16 { get { return _converters[typeof(Int16)] as ObjectFormatter; } }
        public ObjectFormatter INT32 { get { return _converters[typeof(Int32)] as ObjectFormatter; } }
    }

    /**
     * A hack since we cannot cast templated classes to base class and use it. All formatters
     * need to implement this for the specific type and expose the correct functions.
     * */
    public interface ObjectFormatter
    {
        /**
         * Function implements the serialization function for the given data. The class
         * can assume that the passed object data is the type the given formatter handles.
         * */
        byte[] serialize(object data);

        /**
         * Function implements the deserialization logic for the given byte stream into 
         * the format handled by this formatter. This should not assume that the data
         * passed in is of the correct length for deserialization. It should serialize
         * as much as possible and return the length serialized in len parameter.
         * */
        object deserialize(byte[] data, ref int len);

        /**
         * This function returns the length of the byte array that will be created if
         * the given object is formatted.
         * */
        int Length(object o);
    }

    /**
     * Formats string data using the LittleEndianFormatter. 
     * */
    public class StringFormatter : LittleEndianFormatter<string>, ObjectFormatter
    {
        Formatter _formatter;   //The formatter that stores this formatter, used for formatting other types

        //can be constructed only by this namespace.
        internal StringFormatter(Formatter formatter)
        {
            _formatter = formatter;
        }

        //returns the length of the serialized version of this object
        public int Length(object o)
        {
            string val = (string)o;
            int len = 2 + val.Length;
            return len;
        }

        //serializes the string passed and returns the byte array
        public byte[] serialize(object data)
        {
            string val = (string)data;
            UInt16 len = (UInt16)val.Length;
            byte[] blen = _formatter.UINT16.serialize(len);
            byte[] converted = base.convert((string)data);
            ByteBuffer buff = new ByteBuffer(len + 2);
            buff.Append(blen, 0, 2);
            buff.Append(converted, 0, len);

            return buff.getData();
        }

        //deserializes the byte array as a string and returns the length in len.
        //does not assume that the byte passed is exact length. If the total message
        //is not present, this will throw a PartialMessageException.
        public object deserialize(byte[] data, ref int len)
        {
            if (data.Length < 2) throw new PartialMessageException();
            byte[] blen = new byte[2];
            blen[0] = data[0];
            blen[1] = data[1];
            int thislen = 0;
            UInt16 val = (UInt16)_formatter.UINT16.deserialize(blen, ref thislen);
            IConvertible valconv = val;
            len = valconv.ToInt32(null);
            if (data.Length < (len + 2)) throw new PartialMessageException();
            byte[] str = new byte[len];
            for (int i = 0; i < len; i++)
                str[i] = data[i + 2];

            len += 2; //addthe two bytes for length
            return base.convert(str);
        }
    }

    //class to format boolean variables. Uses the LittleEndian format to create bytes
    public class BooleanFormatter : LittleEndianFormatter<Boolean>, ObjectFormatter
    {
        /**
         * Returns the length of a boolean variable.
         * */
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(Boolean));
            return len;
        }

        //serializes a boolean variable into bytes
        public byte[] serialize(object data)
        {
            return base.convert((Boolean)data);
        }

        //deserializes bytes into a boolean variable
        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(Boolean));
            return base.convert(data);
        }
    }

    //class to format a byte value
    public class ByteFormatter : LittleEndianFormatter<Byte>, ObjectFormatter
    {
        //returns the length of byte
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(Byte));
            return len;
        }

        //serializes the byte object into a byte array
        public byte[] serialize(object data)
        {
            return base.convert((Byte)data);
        }

        //deserializes a byte array into a byte value
        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(Byte));
            return base.convert(data);
        }
    }

    //formats a UInt16 into a byte array and back
    public class UInt16Formatter : LittleEndianFormatter<UInt16>, ObjectFormatter
    {
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(UInt16));
            return len;
        }

        public byte[] serialize(object data)
        {
            return base.convert((UInt16)data);
        }

        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(UInt16));
            return base.convert(data);
        }
    }

    //formats a UInt32 into a bytearray and back
    public class UInt32Formatter : LittleEndianFormatter<UInt32>, ObjectFormatter
    {
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(UInt32));
            return len;
        }

        public byte[] serialize(object data)
        {
            return base.convert((UInt32)data);
        }

        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(UInt32));
            return base.convert(data);
        }
    }

    public class Int16Formatter : LittleEndianFormatter<Int16>, ObjectFormatter
    {
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(Int16));
            return len;
        }

        public byte[] serialize(object data)
        {
            return base.convert((Int16)data);
        }

        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(Int16));
            return base.convert(data);
        }
    }

    public class Int32Formatter : LittleEndianFormatter<Int32>, ObjectFormatter
    {
        public int Length(object o)
        {
            int len = Marshal.SizeOf(typeof(UInt32));
            return len;
        }

        public byte[] serialize(object data)
        {
            return base.convert((Int32)data);
        }

        public object deserialize(byte[] data, ref int len)
        {
            len = Marshal.SizeOf(typeof(Int32));
            return base.convert(data);
        }
    }

    /**
     * Class to format into a byte[] in the LittleEndianFormat.
     * */
    public class LittleEndianFormatter<T> : EndianFormatter<T> where T : IConvertible
    {
        //UTF8 encoder.
        System.Text.UTF8Encoding _encoding = new System.Text.UTF8Encoding();

        internal LittleEndianFormatter()
        {
        }

        public byte[] convert(T data)
        {
            Type t = typeof(T);
            int bytemask = 0xFF;
            byte[] ret = null;
            int bytesz = 8;

            if ((data is System.UInt16) || (data is System.UInt32) || (data is System.Byte)
                || (data is System.Boolean) || (data is Int16) || (data is Int32))
            {
                int sz = Marshal.SizeOf(t);
                ret = new byte[sz];
                //any of this can be converted to a 4 byte data and just the relevant data picked up
                int d = data.ToInt32(null);
                for (int i = 0; i < sz; i++)
                    ret[i] = (byte)((d >> (bytesz * i)) & bytemask);
            }
            else if (data is System.String)
            {
                ret = _encoding.GetBytes(data as string);
            }
            else if (data is System.UInt64)
            {
                //TODO: have to implement
            }
            	
            return ret;
        }
        
        public T convert(byte[] data)
        {
            Type t = typeof(T);
            T ret = default(T);
            
            int bytesz = 8;

            if ((t.Equals(typeof(System.UInt16))) || (t.Equals(typeof(System.UInt32))) 
                || (t.Equals(typeof(System.Byte)))
                || (t.Equals(typeof(System.Boolean)))
                || (t.Equals(typeof(System.Int16)))
                || (t.Equals(typeof(System.Int32))))
            {
                int sz = Marshal.SizeOf(t);
                if (data.Length < sz) throw new PartialMessageException();
                UInt32 val = 0;
                for (int i = 0; i < sz; i++)
                    val += ((UInt32)(data[i] << (bytesz * i)));

                IConvertible conv = val;
                ret = (T)conv.ToType(t, null);
            }
            else if (t.Equals(typeof(System.String)))
            {
                String val = _encoding.GetString(data);
                IConvertible conv = val;
                ret = (T)conv.ToType(t, null);
            }
            else if (t.Equals(typeof(System.UInt64)))
            {
                //TODO: must implement
            }
                
            return ret;
        }        
    }
}
